﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Gallio.Framework;
using MbUnit.Framework;
using MbUnit.Framework.ContractVerifiers;
using System.IO;
using PTP.Mock;
using PTP.TestUtil; //extension methods

namespace PTP.Core
{
  [TestFixture]
  public class ContainerStreamTest
  {
    static ContainerHeader addResponseHeader(int transactionID, Stream s)
    {
      return addHeader(ContainerType.ResponseBlock, transactionID, s);
    }

    static ContainerHeader addHeader(ContainerType containerType, int transactionID, Stream s)
    {
      long currentPosition = s.Position;

      byte[] buffer = new byte[12];
      ContainerHeader header = new ContainerHeader();
      header.Length = ContainerHeader.HeaderLength; //this message contains only header
      header.ContainerType = containerType;
      header.TransactionID = transactionID;
      header.WriteBytes(buffer);

      s.Write(buffer, 0, buffer.Length);
      s.Position = currentPosition;

      return header;
    }

    [Test]
    [Row(ContainerType.Undefined, typeof(Container), ExpectedException=typeof(Exception))]
    [Row(ContainerType.CommandBlock, typeof(Container), ExpectedException = typeof(Exception))]
    [Row(ContainerType.EventBlock, typeof(Container), ExpectedException = typeof(Exception))] // We should probably accept EventBlock..
    [Row(ContainerType.DataBlock, typeof(DataBlockContainer))]
    [Row(ContainerType.ResponseBlock, typeof(ResponseBlockContainer))]
    public void ReadContainer_TestType(ContainerType containerType, Type expectedType)
    {
      using (MemoryStream ms = new MemoryStream())
      {
        //initialize data
        int transactionID = 5;
        addHeader(containerType, transactionID, ms);
        //Read data with container stream
        ContainerStream s = new ContainerStream(ms);
        Container container = s.ReadContainer();
        Assert.AreEqual<Type>(expectedType, container.GetType());
        Assert.AreEqual<int>(transactionID, container.Header.TransactionID);
      }
    }

    [Test]
    [Row(1, 2, ExpectedException = typeof(Exception))]
    [Row(0, 0)]
    [Row(1, 1)]
    [Row(1, 1)]
    [Row(0xfffffffe, 0xfffffffe)]
    public void ReadContainer_TestTransactionID(uint transactionID, uint expectedTransactionID)
    {
      using (MemoryStream ms = new MemoryStream())
      {
        //Initialize data
        ContainerHeader header = addResponseHeader((int)transactionID, ms);

        //Read data with container stream
        ContainerStream s = new ContainerStream(ms);
        ResponseBlockContainer response = (ResponseBlockContainer)s.ReadContainer((int)expectedTransactionID, false);
        Assert.AreEqual<int>(header.TransactionID, response.Header.TransactionID);
        Assert.AreEqual<int>((int)expectedTransactionID, response.Header.TransactionID);
      }
    }

    [Test]
    [Description("Read response that contains data container and response container, but skip the data payload")]
    [Row(new byte[] { // one data and response pair
      0x0E, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, // data header
      0x00, 0x00, // data payload
      0x0C, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x20, 0x00, 0x00, 0x00, 0x00 // response header
    })]
    [Row(new byte[] { // two data and response pairs
      0x0E, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, // data header
      0x00, 0x00, // data payload
      0x0C, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x20, 0x00, 0x00, 0x00, 0x00, // response header
      0x0E, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, // data header
      0x00, 0x00, // data payload
      0x0C, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x20, 0x00, 0x00, 0x00, 0x00 // response header
    })]
    public void ReadContainer_DataResponseSkipData(byte[] data)
    {
      using (MockStream ms = new MockStream(data))
      {
        //Read data with container stream
        ContainerStream s = new ContainerStream(ms);

        while (ms.Position < ms.Length)
        {
          //data
          Container response = s.ReadContainer();
          Assert.AreEqual<ContainerType>(response.Header.ContainerType, ContainerType.DataBlock);
          Assert.IsInstanceOfType<DataBlockContainer>(response);

          //reponse
          response = s.ReadContainer();
          Assert.AreEqual<ContainerType>(response.Header.ContainerType, ContainerType.ResponseBlock);
          Assert.IsInstanceOfType<ResponseBlockContainer>(response);
        }
      }
    }

    [Test]
    [Row(-1, new byte[] { 0x00, 0x00 }, ExpectedException = typeof(IOException))]
    [Row(4, new byte[] { 0x01, 0x00, 0x00, 0x00 })]
    [Row(4, new byte[] { 0x01, 0x00, 0x00, 0x00, 0x00 }, ExpectedException = typeof(IOException))]
    public void WriteHeaderAndWrite(int payloadLength, byte[] data)
    {
      using (MockStream ms = new MockStream(new byte[] { }))
      {
        ContainerStream s = new ContainerStream(ms);
        ContainerHeader header = new ContainerHeader();
        header.Code = (int)CommandCode.GetStorageInfo;
        header.PayloadLength = payloadLength;
        header.TransactionID = 1;
        if (payloadLength > -1)
        {
          s.WriteHeader(header);
        }

        //Write data in two byte packages
        for (int i = 0; i < data.Length; i += 2)
          s.Write(data, i, (data.Length < i+2)?i+2-data.Length:2);

        //construct expected bytes
        byte[] expectedBytes = new byte[ContainerHeader.HeaderLength + data.Length];
        header.WriteBytes(expectedBytes);
        data.CopyTo(expectedBytes, ContainerHeader.HeaderLength);
        Assert.AreElementsEqual<byte>(expectedBytes, ms.GetSentData(true));
      }
    }

    [Test]
    public void WriteCommandBlockContainer()
    {
      using (MockStream ms = new MockStream(new byte[] { }))
      {
        CommandBlockContainer c = new CommandBlockContainer((int)CommandCode.GetStorageInfo, 1);
        WriteContainer(c, ms);
        Assert.AreElementsEqual<byte>(c.GetBytes(), ms.GetSentData(true));
      }
    }

    public void WriteContainer(Container c, Stream s)
    {
      ContainerStream cs = new ContainerStream(s);
      cs.WriteContainer(c);
    }


    [Test]
    [Description("The Still Image Bulk-only Protocol, which is based on the PIMA15740 protocol, does not allow queuing of operations.")]
    public void ReadContainer_WriteQueue()
    {
      //This test doesn't read incoming data
      using (MockStream ms = new MockStream(new byte[] {}))
      {
        ContainerStream s = new ContainerStream(ms);
      }
    }
  }
}
